﻿#region LGPL Header
// Copyright (C) 2020, Jackie Ng
// https://github.com/jumpinjackie/fdotoolbox, jumpinjackie@gmail.com
// 
// This library is free software; you can redistribute it and/or
// modify it under the terms of the GNU Lesser General Public
// License as published by the Free Software Foundation; either
// version 2.1 of the License, or (at your option) any later version.
// 
// This library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
// Lesser General Public License for more details.
// 
// You should have received a copy of the GNU Lesser General Public
// License along with this library; if not, write to the Free Software
// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
// 
//
// See license.txt for more/additional licensing information
#endregion
using FdoToolbox.Core.Utility;
using OSGeo.FDO.Connections;
using OSGeo.FDO.Schema;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Security.Policy;
using Res = FdoToolbox.Core.ResourceUtil;

namespace FdoToolbox.Core.Feature
{
    public class SchemaCapabilityChecker
    {
        readonly IConnection _conn;

        public SchemaCapabilityChecker(IConnection conn)
        {
            _conn = conn;
        }

        public bool CanApplySchema(FeatureSchema schema, out IncompatibleSchema incSchema)
        {
            incSchema = null;
            foreach (ClassDefinition classDef in schema.Classes)
            {
                //Ignore deleted classes
                if (classDef.ElementState == SchemaElementState.SchemaElementState_Deleted)
                    continue;

                IncompatibleClass cls;
                if (!CanApplyClass(classDef, out cls))
                {
                    if (cls != null)
                    {
                        if (incSchema == null)
                            incSchema = new IncompatibleSchema(schema.Name);

                        incSchema.AddClass(cls);
                    }
                }
            }

            return (incSchema == null);
        }

        public bool CanApplyClass(ClassDefinition classDef, out IncompatibleClass cls)
        {
            cls = null;
            using (var schemaCaps = _conn.SchemaCapabilities)
            using (var geomCaps = _conn.GeometryCapabilities)
            {
                string className = classDef.Name;
                ClassType ctype = classDef.ClassType;

                if (Array.IndexOf<ClassType>(schemaCaps.ClassTypes, ctype) < 0)
                {
                    string classReason = "Un-supported class type";
                    AddIncompatibleClass(className, ref cls, classReason, IncompatibleClassReason.UnsupportedClassType);
                }
                if (!schemaCaps.SupportsCompositeId && classDef.IdentityProperties.Count > 1)
                {
                    string classReason = "Multiple identity properties (composite id) not supported";
                    AddIncompatibleClass(className, ref cls, classReason, IncompatibleClassReason.UnsupportedCompositeKeys);
                }
                if (!schemaCaps.SupportsInheritance && classDef.BaseClass != null)
                {
                    string classReason = "Class inherits from a base class (inheritance not supported)";
                    AddIncompatibleClass(className, ref cls, classReason, IncompatibleClassReason.UnsupportedInheritance);
                }
                var clsProps = classDef.Properties;
                foreach (PropertyDefinition propDef in clsProps)
                {
                    //Ignore deleted properties
                    if (propDef.ElementState == SchemaElementState.SchemaElementState_Deleted)
                        continue;

                    string propName = propDef.Name;
                    DataPropertyDefinition dataDef = propDef as DataPropertyDefinition;
                    //AssociationPropertyDefinition assocDef = propDef as AssociationPropertyDefinition;
                    GeometricPropertyDefinition geomDef = propDef as GeometricPropertyDefinition;
                    //RasterPropertyDefinition rasterDef = propDef as RasterPropertyDefinition;
                    ObjectPropertyDefinition objDef = propDef as ObjectPropertyDefinition;
                    if (!schemaCaps.SupportsAutoIdGeneration)
                    {
                        if (dataDef != null && dataDef.IsAutoGenerated)
                        {
                            string classReason = "Class has unsupported auto-generated properties";
                            string propReason = "Unsupported auto-generated id";

                            AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedIdentityProperty);
                        }
                    }
                    else
                    {
                        if (dataDef != null && dataDef.IsAutoGenerated)
                        {
                            if (Array.IndexOf<DataType>(schemaCaps.SupportedAutoGeneratedTypes, dataDef.DataType) < 0)
                            {
                                string classReason = "Class has unsupported auto-generated data type";
                                string propReason = "Unsupported auto-generated data type: " + dataDef.DataType;
                                AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedAutoGeneratedType);
                            }
                        }
                    }
                    if (geomDef != null)
                    {
                        var currentGts = geomDef.SpecificGeometryTypes.ToHashSet();
                        var supportedGts = geomCaps.GeometryTypes.ToHashSet();
                        if (!currentGts.IsSubsetOf(supportedGts))
                        {
                            currentGts.RemoveWhere(gt => supportedGts.Contains(gt));
                            string classReason = "Class has unsupported geometric property";
                            string propReason = "Unsupported geometry types: " + string.Join(",", currentGts);
                            AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedGeometryTypes);
                        }
                    }
                    if (dataDef != null && classDef.IdentityProperties.Contains(dataDef))
                    {
                        if (Array.IndexOf<DataType>(schemaCaps.SupportedIdentityPropertyTypes, dataDef.DataType) < 0)
                        {
                            string classReason = "Class has unsupported identity property data type";
                            string propReason = "Unsupported identity property data type";
                            AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedIdentityPropertyType);
                        }
                    }
                    if (!schemaCaps.SupportsAssociationProperties)
                    {
                        if (propDef.PropertyType == PropertyType.PropertyType_AssociationProperty)
                        {
                            string classReason = "Class has unsupported association properties";
                            string propReason = "Unsupported association property type";

                            AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedAssociationProperties);
                        }
                    }
                    if (!schemaCaps.SupportsDefaultValue)
                    {
                        if (dataDef != null && !string.IsNullOrEmpty(dataDef.DefaultValue))
                        {
                            string classReason = "Class has properties with unsupported default values";
                            string propReason = "Default values not supported";

                            AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedDefaultValues);
                        }
                    }
                    if (!schemaCaps.SupportsExclusiveValueRangeConstraints)
                    {
                        if (dataDef != null && dataDef.ValueConstraint != null && dataDef.ValueConstraint.ConstraintType == PropertyValueConstraintType.PropertyValueConstraintType_Range)
                        {
                            PropertyValueConstraintRange range = dataDef.ValueConstraint as PropertyValueConstraintRange;
                            if (!range.MaxInclusive && !range.MinInclusive)
                            {
                                string classReason = "Class has properties with unsupported exclusive range constraints";
                                string propReason = "Exclusive range constraint not supported";

                                AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedExclusiveValueRangeConstraints);
                            }
                        }
                    }
                    if (!schemaCaps.SupportsInclusiveValueRangeConstraints)
                    {
                        if (dataDef != null && dataDef.ValueConstraint != null && dataDef.ValueConstraint.ConstraintType == PropertyValueConstraintType.PropertyValueConstraintType_Range)
                        {
                            PropertyValueConstraintRange range = dataDef.ValueConstraint as PropertyValueConstraintRange;
                            if (range.MaxInclusive && range.MinInclusive)
                            {
                                string classReason = "Class has properties with unsupported inclusive range constraints";
                                string propReason = "Inclusive range constraint not supported";

                                AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedInclusiveValueRangeConstraints);
                            }
                        }
                    }
                    if (!schemaCaps.SupportsNullValueConstraints)
                    {
                        if (dataDef != null && dataDef.Nullable)
                        {
                            string classReason = "Class has unsupported nullable properties";
                            string propReason = "Null value constraints not supported";

                            AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedNullValueConstraints);
                        }
                    }
                    if (!schemaCaps.SupportsObjectProperties)
                    {
                        if (objDef != null)
                        {
                            string classReason = "Class has unsupported object properties";
                            string propReason = "Object properties not supported";

                            AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedObjectProperties);
                        }
                    }
                    if (!schemaCaps.SupportsUniqueValueConstraints)
                    {
                        //
                    }
                    if (!schemaCaps.SupportsValueConstraintsList)
                    {
                        if (dataDef != null && dataDef.ValueConstraint != null && dataDef.ValueConstraint.ConstraintType == PropertyValueConstraintType.PropertyValueConstraintType_List)
                        {
                            string classReason = "Class has properties with unsupported value list constraints";
                            string propReason = "value list constraints not supported";

                            AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedValueListConstraints);
                        }
                    }

                    if (dataDef != null)
                    {
                        if (Array.IndexOf<DataType>(schemaCaps.DataTypes, dataDef.DataType) < 0)
                        {
                            string classReason = "Class has properties with unsupported data type: " + dataDef.DataType;
                            string propReason = "Unsupported data type: " + dataDef.DataType;

                            AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.UnsupportedDataType);
                        }
                    }

                    if (dataDef != null && dataDef.Length == 0)
                    {
                        if (dataDef.DataType == DataType.DataType_String || dataDef.DataType == DataType.DataType_BLOB || dataDef.DataType == DataType.DataType_CLOB)
                        {
                            string classReason = "Class has a string/BLOB/CLOB property of zero-length";
                            string propReason = "Zero-length property";

                            AddIncompatibleProperty(className, ref cls, propName, classReason, propReason, IncompatiblePropertyReason.ZeroLengthProperty);
                        }
                    }
                }
                return (cls == null);
            }
        }

        public FeatureSchema AlterSchema(FeatureSchema schema, IncompatibleSchema incompatibleSchema, Func<SpatialContextInfo> getActiveSpatialContext)
        {
            //DO NOT clone the schema, as this resets the state of the cloned schema to added, which
            //can trip up the IApplySchema command. Anyway RejectChanges() is the ultimate reset button
            //should we ever want this schema in its initial state.
            FeatureSchema altSchema = schema;

            //Process each incompatible class
            foreach (IncompatibleClass incClass in incompatibleSchema.Classes)
            {
                int cidx = altSchema.Classes.IndexOf(incClass.Name);
                if (cidx >= 0)
                {
                    ClassDefinition classDef = altSchema.Classes[cidx];
                    classDef = AlterClassDefinition(classDef, incClass, getActiveSpatialContext, null);
                }
                else
                {
                    throw new FeatureServiceException(Res.GetStringFormatted("ERR_INCOMPATIBLE_CLASS_NOT_FOUND", incClass.Name));
                }
            }

            return altSchema;
        }

        /// <summary>
        /// Alters the given class definition to be compatible with the current connection
        /// </summary>
        /// <param name="classDef"></param>
        /// <param name="incClass"></param>
        /// <param name="getActiveSpatialContext"></param>
        /// <param name="fixGeomSc"></param>
        /// <returns></returns>
        public ClassDefinition AlterClassDefinition(ClassDefinition classDef,
                                                    IncompatibleClass incClass,
                                                    Func<SpatialContextInfo> getActiveSpatialContext,
                                                    Action<GeometricPropertyDefinition, SpatialContextInfo> fixGeomSc)
        {
            var clsProps = classDef.Properties;
            //Process each incompatible property
            foreach (IncompatibleProperty incProp in incClass.Properties)
            {
                int pidx = clsProps.IndexOf(incProp.Name);
                if (pidx >= 0)
                {
                    PropertyDefinition prop = clsProps[pidx];
                    AlterProperty(ref classDef, ref prop, incProp.ReasonCodes);
                }
                else
                {
                    throw new FeatureServiceException(Res.GetStringFormatted("ERR_INCOMPATIBLE_PROPERTY_NOT_FOUND", incProp.Name, incClass.Name));
                }
            }
            AlterClass(ref classDef, incClass.ReasonCodes);

            //Fix the spatial context association
            var scInfo = getActiveSpatialContext();
            clsProps = classDef.Properties;
            foreach (PropertyDefinition pd in clsProps)
            {
                if (pd.PropertyType == PropertyType.PropertyType_GeometricProperty)
                {
                    GeometricPropertyDefinition g = pd as GeometricPropertyDefinition;
                    if (fixGeomSc != null)
                    {
                        fixGeomSc(g, scInfo);
                    }
                    else
                    {
                        if (scInfo != null)
                            g.SpatialContextAssociation = scInfo.Name;
                        else
                            g.SpatialContextAssociation = string.Empty;
                    }
                }
            }

            //If we fixed any data properties that are also identity, the property in the id
            //prop collection are still on the old settings (they're probably clones?), so we'll
            //clear and re-add such properties
            var idProps = classDef.IdentityProperties;
            var idNames = new HashSet<string>();
            foreach (DataPropertyDefinition dp in idProps)
            {
                idNames.Add(dp.Name);
            }
            idProps.Clear();
            foreach (var pn in idNames)
            {
                var pidx = clsProps.IndexOf(pn);
                if (pidx >= 0)
                {
                    var dp = clsProps[pidx] as DataPropertyDefinition;
                    if (dp != null)
                    {
                        //Make sure that for any identity properties that are auto-generated that IsNullable is false
                        if (dp.IsAutoGenerated && dp.Nullable)
                        {
                            dp.Nullable = false;
                        }
                        idProps.Add(dp);
                    }
                }
            }

            //Finally make sure the data properties lie within the limits of this
            //connection
            FixDataProperties(ref classDef);

            return classDef;
        }

        /// <summary>
        /// Checks and modifies the lengths of any [blob/clob/string/decimal] data 
        /// properties inside the class definition so that it lies within the limits 
        /// defined in this connection.
        /// </summary>
        /// <param name="classDef"></param>
        /// <returns></returns>
        internal void FixDataProperties(ref ClassDefinition classDef)
        {
            using (var schemaCaps = _conn.SchemaCapabilities)
            {
                foreach (PropertyDefinition propDef in classDef.Properties)
                {
                    DataPropertyDefinition dp = propDef as DataPropertyDefinition;
                    if (dp != null)
                    {
                        switch (dp.DataType)
                        {
                            case DataType.DataType_BLOB:
                                {
                                    int length = (int)schemaCaps.get_MaximumDataValueLength(DataType.DataType_BLOB);
                                    if (dp.Length > length && length > 0)
                                        dp.Length = length;
                                }
                                break;
                            case DataType.DataType_CLOB:
                                {
                                    int length = (int)schemaCaps.get_MaximumDataValueLength(DataType.DataType_CLOB);
                                    if (dp.Length > length && length > 0)
                                        dp.Length = length;
                                }
                                break;
                            case DataType.DataType_String:
                                {
                                    int length = (int)schemaCaps.get_MaximumDataValueLength(DataType.DataType_String);
                                    if (dp.Length > length && length > 0)
                                        dp.Length = length;
                                }
                                break;
                            case DataType.DataType_Decimal:
                                {
                                    if (dp.Precision > schemaCaps.MaximumDecimalPrecision)
                                        dp.Precision = schemaCaps.MaximumDecimalPrecision;

                                    if (dp.Scale > schemaCaps.MaximumDecimalScale)
                                        dp.Scale = schemaCaps.MaximumDecimalScale;
                                }
                                break;
                        }
                    }
                }
            }
        }

        private void AlterClass(ref ClassDefinition classDef, ISet<IncompatibleClassReason> reasons)
        {
            if (reasons.Count == 0)
                return;

            using (var schemaCaps = _conn.SchemaCapabilities)
            {
                var clsProps = classDef.Properties;
                var clsIdProps = classDef.IdentityProperties;
                foreach (IncompatibleClassReason reason in reasons)
                {
                    switch (reason)
                    {
                        case IncompatibleClassReason.UnsupportedAutoProperties:
                            {
                                //AlterProperty() should have dealt with this
                            }
                            break;
                        case IncompatibleClassReason.UnsupportedClassType:
                            {
                                throw new FeatureServiceException(Res.GetString("ERR_UNABLE_TO_CONVERT_CLASS"));
                            }
                        case IncompatibleClassReason.UnsupportedCompositeKeys:
                            {
                                //Remove identity properties and replace with an auto-generated property
                                DataPropertyDefinition id = new DataPropertyDefinition("Autogenerated_ID", "");
                                DataType[] idTypes = new DataType[schemaCaps.SupportedAutoGeneratedTypes.Length];
                                Array.Copy(schemaCaps.SupportedAutoGeneratedTypes, idTypes, idTypes.Length);
                                Array.Sort<DataType>(idTypes, new DataTypeComparer());
                                id.DataType = idTypes[idTypes.Length - 1];
                                id.IsAutoGenerated = true;

                                clsIdProps.Clear();
                                clsProps.Add(id);
                                clsIdProps.Add(id);
                            }
                            break;
                        case IncompatibleClassReason.UnsupportedInheritance:
                            {
                                //Move base class properties to derived, prefix properties with BASE_
                                var baseClass = classDef.BaseClass;
                                var properties = new List<PropertyDefinition>();
                                var ids = new List<DataPropertyDefinition>();
                                var baseClsProps = baseClass.Properties;
                                foreach (PropertyDefinition propDef in baseClsProps)
                                {
                                    DataPropertyDefinition dp = propDef as DataPropertyDefinition;
                                    PropertyDefinition pd = FdoSchemaUtil.CloneProperty(propDef);
                                    pd.Name = "BASE_" + pd.Name;
                                    properties.Add(pd);
                                    if (dp != null && baseClsProps.Contains(dp))
                                        ids.Add(pd as DataPropertyDefinition);
                                }
                                classDef.BaseClass = null;
                                foreach (PropertyDefinition pd in properties)
                                {
                                    clsProps.Add(pd);
                                }
                                foreach (DataPropertyDefinition id in ids)
                                {
                                    clsIdProps.Add(id);
                                }
                            }
                            break;
                    }
                }
            }
        }

        private void AlterProperty(ref ClassDefinition classDef, ref PropertyDefinition prop, ISet<IncompatiblePropertyReason> reasons)
        {
            if (reasons.Count == 0)
                return;

            DataPropertyDefinition dp = prop as DataPropertyDefinition;
            GeometricPropertyDefinition gp = prop as GeometricPropertyDefinition;
            using (var geomCaps = _conn.GeometryCapabilities)
            using (var schemaCaps = _conn.SchemaCapabilities)
            {
                foreach (IncompatiblePropertyReason reason in reasons)
                {
                    switch (reason)
                    {
                        case IncompatiblePropertyReason.UnsupportedGeometryTypes:
                            {
                                var currentGts = gp.SpecificGeometryTypes.ToHashSet();
                                var supportedGts = geomCaps.GeometryTypes.ToHashSet();
                                // In the absence of any finesse, just remove all unsupported geometry types.
                                currentGts.RemoveWhere(gt => !supportedGts.Contains(gt));
                                /*
                                GeometricType gt = (GeometricType)0;
                                foreach (var gts in currentGts)
                                {
                                    switch (gts)
                                    {
                                        case OSGeo.FDO.Common.GeometryType.GeometryType_CurveString:
                                        case OSGeo.FDO.Common.GeometryType.GeometryType_LineString:
                                        case OSGeo.FDO.Common.GeometryType.GeometryType_MultiLineString:
                                            gt |= GeometricType.GeometricType_Curve;
                                            break;
                                        case OSGeo.FDO.Common.GeometryType.GeometryType_MultiPoint:
                                        case OSGeo.FDO.Common.GeometryType.GeometryType_Point:
                                            gt |= GeometricType.GeometricType_Point;
                                            break;
                                        case OSGeo.FDO.Common.GeometryType.GeometryType_Polygon:
                                        case OSGeo.FDO.Common.GeometryType.GeometryType_MultiPolygon:
                                            gt |= GeometricType.GeometricType_Surface;
                                            break;
                                    }
                                }
                                */
                                gp.SpecificGeometryTypes = currentGts.ToArray();
                                //gp.GeometryTypes = (int)gt;
                            }
                            break;
                        case IncompatiblePropertyReason.UnsupportedAssociationProperties:
                            //Can't do anything here
                            throw new FeatureServiceException(Res.GetString("ERR_CANNOT_ALTER_ASSOCIATION"));
                        case IncompatiblePropertyReason.UnsupportedDataType:
                            {
                                //Try to promote data type
                                DataType dt = GetPromotedDataType(dp.DataType, schemaCaps.DataTypes);
                                dp.DataType = dt;
                                if (dp.DataType == DataType.DataType_BLOB ||
                                    dp.DataType == DataType.DataType_CLOB ||
                                    dp.DataType == DataType.DataType_String)
                                {
                                    dp.Length = (int)schemaCaps.get_MaximumDataValueLength(dp.DataType);
                                }
                                if (dp.DataType == DataType.DataType_Decimal)
                                {
                                    dp.Scale = schemaCaps.MaximumDecimalScale;
                                    dp.Precision = schemaCaps.MaximumDecimalPrecision;
                                }
                            }
                            break;
                        case IncompatiblePropertyReason.UnsupportedAutoGeneratedType:
                            {
                                //Try to promote data type
                                try
                                {
                                    DataType dt = GetPromotedDataType(dp.DataType, schemaCaps.SupportedAutoGeneratedTypes);
                                    dp.DataType = dt;
                                    if (dp.DataType == DataType.DataType_BLOB ||
                                        dp.DataType == DataType.DataType_CLOB ||
                                        dp.DataType == DataType.DataType_String)
                                    {
                                        long length = schemaCaps.get_MaximumDataValueLength(dp.DataType);
                                        if (length <= 0)
                                            length = 255;
                                        dp.Length = (int)length;
                                    }
                                    if (dp.DataType == DataType.DataType_Decimal)
                                    {
                                        dp.Scale = schemaCaps.MaximumDecimalScale;
                                        dp.Precision = schemaCaps.MaximumDecimalPrecision;
                                    }
                                }
                                catch (FeatureServiceException) //Thrown if we can't get a promoted data type
                                {
                                    if (dp.IsAutoGenerated && schemaCaps.SupportedAutoGeneratedTypes.Any())
                                    {
                                        // Fortunately, the DataType enum is mostly ordered from narrowest to
                                        // widest, so pick the widest support autogenerated data type
                                        dp.DataType = schemaCaps.SupportedAutoGeneratedTypes.Max();
                                    }
                                }
                            }
                            break;
                        case IncompatiblePropertyReason.UnsupportedDefaultValues:
                            {
                                //Remove default value
                                dp.DefaultValue = string.Empty;
                            }
                            break;
                        case IncompatiblePropertyReason.UnsupportedExclusiveValueRangeConstraints:
                            {
                                //Remove constraint
                                dp.ValueConstraint = null;
                            }
                            break;
                        case IncompatiblePropertyReason.UnsupportedIdentityProperty:
                            {
                                //Remove from identity property list
                                classDef.IdentityProperties.Remove(dp);
                            }
                            break;
                        case IncompatiblePropertyReason.UnsupportedIdentityPropertyType:
                            {
                                // May have been already promoted/demoted from a UnsupportedAutoGeneratedType correction
                                if (!schemaCaps.SupportedIdentityPropertyTypes.Contains(dp.DataType))
                                {
                                    //Try to promote data type
                                    DataType dt = GetPromotedDataType(dp.DataType, schemaCaps.SupportedIdentityPropertyTypes);
                                    dp.DataType = dt;
                                    if (dp.DataType == DataType.DataType_BLOB ||
                                        dp.DataType == DataType.DataType_CLOB ||
                                        dp.DataType == DataType.DataType_String)
                                    {
                                        dp.Length = (int)schemaCaps.get_MaximumDataValueLength(dp.DataType);
                                    }
                                    if (dp.DataType == DataType.DataType_Decimal)
                                    {
                                        dp.Scale = schemaCaps.MaximumDecimalScale;
                                        dp.Precision = schemaCaps.MaximumDecimalPrecision;
                                    }
                                }
                            }
                            break;
                        case IncompatiblePropertyReason.UnsupportedInclusiveValueRangeConstraints:
                            {
                                //Remove constraint
                                dp.ValueConstraint = null;
                            }
                            break;
                        case IncompatiblePropertyReason.UnsupportedNullValueConstraints:
                            {
                                //Make nullable = false
                                dp.Nullable = false;
                            }
                            break;
                        case IncompatiblePropertyReason.UnsupportedObjectProperties:
                            //Can't do anything here
                            throw new FeatureServiceException(Res.GetString("ERR_CANNOT_ALTER_OBJECT"));
                        case IncompatiblePropertyReason.UnsupportedUniqueValueConstraints:
                            {
                                //Remove constraint
                                dp.ValueConstraint = null;
                            }
                            break;
                        case IncompatiblePropertyReason.UnsupportedValueListConstraints:
                            {
                                //Remove constraint
                                dp.ValueConstraint = null;
                            }
                            break;
                        case IncompatiblePropertyReason.ZeroLengthProperty:
                            {
                                //Set length to maximum supported value or 255 if provider returns a nonsensical value
                                long length = schemaCaps.get_MaximumDataValueLength(dp.DataType);
                                if (length <= 0)
                                    length = 255;
                                dp.Length = (int)length;
                            }
                            break;
                    }
                }
            }
        }

        /// <summary>
        /// Gets the type of the promoted data.
        /// </summary>
        /// <param name="dataType">Type of the data.</param>
        /// <param name="dataTypes">The data types.</param>
        /// <returns></returns>
        public static DataType GetPromotedDataType(DataType dataType, DataType[] dataTypes)
        {
            DataType? dt = null;
            switch (dataType)
            {
                case DataType.DataType_BLOB:
                    throw new FeatureServiceException(Res.GetString("ERR_CANNOT_PROMOTE_BLOB"));
                case DataType.DataType_Boolean:
                    {
                        if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Byte) >= 0)
                            dt = DataType.DataType_Byte;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Int16) >= 0)
                            dt = DataType.DataType_Int16;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Int32) >= 0)
                            dt = DataType.DataType_Int32;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Int64) >= 0)
                            dt = DataType.DataType_Int64;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_String) >= 0)
                            dt = DataType.DataType_String;
                    }
                    break;
                case DataType.DataType_Byte:
                    {
                        if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Int16) >= 0)
                            dt = DataType.DataType_Int16;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Int32) >= 0)
                            dt = DataType.DataType_Int32;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Int64) >= 0)
                            dt = DataType.DataType_Int64;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_String) >= 0)
                            dt = DataType.DataType_String;
                    }
                    break;
                case DataType.DataType_CLOB:
                    throw new FeatureServiceException(Res.GetString("ERR_CANNOT_PROMOTE_CLOB"));
                case DataType.DataType_DateTime:
                    {
                        if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_String) >= 0)
                            dt = DataType.DataType_String;
                    }
                    break;
                case DataType.DataType_Decimal:
                    {
                        if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Double) >= 0)
                            dt = DataType.DataType_Double;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_String) >= 0)
                            dt = DataType.DataType_String;
                    }
                    break;
                case DataType.DataType_Double:
                    {
                        if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_String) >= 0)
                            dt = DataType.DataType_String;
                    }
                    break;
                case DataType.DataType_Int16:
                    {
                        if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Int32) >= 0)
                            dt = DataType.DataType_Int32;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Int64) >= 0)
                            dt = DataType.DataType_Int64;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_String) >= 0)
                            dt = DataType.DataType_String;
                    }
                    break;
                case DataType.DataType_Int32:
                    {
                        if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Int64) >= 0)
                            dt = DataType.DataType_Int64;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_String) >= 0)
                            dt = DataType.DataType_String;
                    }
                    break;
                case DataType.DataType_Int64:
                    {
                        if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_String) >= 0)
                            dt = DataType.DataType_String;
                    }
                    break;
                case DataType.DataType_Single:
                    {
                        if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_Double) >= 0)
                            dt = DataType.DataType_Double;
                        else if (Array.IndexOf<DataType>(dataTypes, DataType.DataType_String) >= 0)
                            dt = DataType.DataType_String;
                    }
                    break;
                case DataType.DataType_String:
                    throw new FeatureServiceException(Res.GetString("ERR_CANNOT_PROMOTE_STRINGS"));
            }

            if (!dt.HasValue)
                throw new FeatureServiceException(Res.GetStringFormatted("ERR_UNSUITABLE_DATA_TYPE_REPLACEMENT", dataType));

            return dt.Value;
        }

        private static void AddIncompatibleClass(string className, ref IncompatibleClass cls, string classReason, IncompatibleClassReason rcode)
        {
            if (cls == null)
                cls = new IncompatibleClass(className, classReason);
            else
                cls.AddReason(classReason);

            cls.ReasonCodes.Add(rcode);
        }

        private static void AddIncompatibleProperty(string className, ref IncompatibleClass cls, string propName, string classReason, string propReason, IncompatiblePropertyReason rcode)
        {
            if (cls == null)
                cls = new IncompatibleClass(className, classReason);
            else
                cls.AddReason(classReason);

            IncompatibleProperty prop = cls.FindProperty(propName);
            if (prop == null)
            {
                prop = new IncompatibleProperty(propName, propReason);
                prop.ReasonCodes.Add(rcode);
                cls.AddProperty(prop);
            }
            else
            {
                prop.AddReason(propReason);
                prop.ReasonCodes.Add(rcode);
            }
        }
    }
}
